QuestHelper_File["warning.lua"] = "4.0.1.$svnversion$"
QuestHelper_Loadtime["warning.lua"] = GetTime()

--[[
  Much of this code is ganked wholesale from Swatter, and is Copyright (C) 2006 Norganna. Licensed under LGPL v3.0.
]]

local debug_output = false
if QuestHelper_File["warning.lua"] == "Development Version" then debug_output = true end

QuestHelper_local_version = QuestHelper_File["warning.lua"]
QuestHelper_toc_version = GetAddOnMetadata("QuestHelper", "Version")

local origHandler = getwarninghandler()

local QuestHelper_WarningCatcher = { }

local startup_warnings = {}
local completely_started = false
local yelled_at_user = false

local first_warning = nil

QuestHelper_Warnings = {}

function QuestHelper_WarningCatcher.TextWarning(text)
  DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff8080QuestHelper Warning Handler: |r%s", text))
end


-- ganked verbatim from Swatter
function QuestHelper_WarningCatcher.GetQuests()
  local return_string = ""

  for q = 1, GetNumQuestLogEntries() do
    local title, _, _, _, header, _, _, _, id = GetQuestLogTitle(q)
    if header then
      if id then
        return_string = return_string .. string.format("%s (%d)\n", title, id)
      else
        return_string = return_string .. string.format("[%s]\n", title)
      end
    else
      return_string = return_string .. string.format("\t%s (%d)\n", title, id)
    end
  end

  return return_string
end
function QuestHelper_WarningCatcher.GetAddOns()
	local addlist = ""
	for i = 1, GetNumAddOns() do
		local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)

		local loaded = IsAddOnLoaded(i)
		if (loaded) then
			if not name then name = "Anonymous" end
			name = name:gsub("[^a-zA-Z0-9]+", "")
			local version = GetAddOnMetadata(i, "Version")
			local class = getglobal(name)
			if not class or type(class)~='table' then class = getglobal(name:lower()) end
			if not class or type(class)~='table' then class = getglobal(name:sub(1,1):upper()..name:sub(2):lower()) end
			if not class or type(class)~='table' then class = getglobal(name:upper()) end
			if class and type(class)=='table' then
				if (class.version) then
					version = class.version
				elseif (class.Version) then
					version = class.Version
				elseif (class.VERSION) then
					version = class.VERSION
				end
			end
			local const = getglobal(name:upper().."_VERSION")
			if (const) then version = const end

			if type(version)=='table' then
        local allstr = true
        for k, v in pairs(version) do
          if type(v) ~= "string" then allstr = false end
        end
        
        if allstr then
          version = table.concat(version,":")
        end
			elseif type(version) == 'function' then
        local yay, v = pcall(version)
        
        if yay then version = v end
      end

			if (version) then
				addlist = addlist.."  "..name..", v"..tostring(version).."\n"
			else
				addlist = addlist.."  "..name.."\n"
			end
		end
	end
	return addlist
end

local warning_uniqueness_whitelist = {
  ["count"] = true,
  ["timestamp"] = true,
}

-- here's the logic
function QuestHelper_WarningCatcher.CondenseWarnings()
  if completely_started then
    while next(startup_warnings) do
      _, warn = next(startup_warnings)
      table.remove(startup_warnings)
      
      if not QuestHelper_Warnings[warn.type] then QuestHelper_Warnings[warn.type] = {} end
      
      local found = false
      
      for _, item in ipairs(QuestHelper_Warnings[warn.type]) do
        local match = true
        for k, v in pairs(warn.dat) do
          if not warning_uniqueness_whitelist[k] and item[k] ~= v then match = false break end
        end
        if match then for k, v in pairs(item) do
          if not warning_uniqueness_whitelist[k] and warn.dat[k] ~= v then match = false break end
        end end
        if match then
          found = true
          item.count = (item.count or 1) + 1
          break
        end
      end
      
      if not found then
        table.insert(QuestHelper_Warnings[warn.type], warn.dat)
      end
    end
  end
end

function QuestHelper_WarningCatcher_RegisterWarning(typ, dat)
  table.insert(startup_warnings, {type = typ, dat = dat})
  QuestHelper_WarningCatcher.CondenseWarnings()
end

function QuestHelper_WarningPackage(depth)
  return {
    timestamp = date("%Y-%m-%d %H:%M:%S"),
    local_version = QuestHelper_local_version,
    toc_version = QuestHelper_toc_version,
    game_version = GetBuildInfo(),
    locale = GetLocale(),
    mutation_passes_exceeded = QuestHelper and QuestHelper.mutation_passes_exceeded,
    stack = debugstack(depth or 4, 20, 20),
  }
end

StaticPopupDialogs["QH_EXPLODEY"] = {
	text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh warning\" for a detailed warning message.",
	button1 = OKAY,
	OnAccept = function(self)
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1
};

function QuestHelper_WarningCatcher_ExplicitWarning(loud, o_msg, o_frame, o_stack, ...)
  local msg = o_msg or ""  

  -- We toss it into StartupWarnings, and then if we're running properly, we'll merge it into the main DB.
  local twarning = QuestHelper_WarningPackage()
  
  twarning.message = msg
  twarning.addons = QuestHelper_WarningCatcher.GetAddOns()
  twarning.stack = o_stack or twarning.stack
  twarning.silent = not loud
  twarning.quests = QuestHelper_WarningCatcher.GetQuests()
  
  QuestHelper_WarningCatcher_RegisterWarning("crash", twarning)
  
  if first_warning and first_warning.silent and not first_warning.next_loud and not twarning.silent then first_warning.next_loud = twarning first_warning.addons = "" end
  if not first_warning or first_warning.generated then first_warning = twarning end
  
  QuestHelper_WarningCatcher.CondenseWarnings()

  if (--[[debug_output or]] loud) and not yelled_at_user then
    --print("qhbroken")
    StaticPopupDialogs["QH_EXPLODEY"] = {
      text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh warning\" for a detailed warning message.",
      button1 = OKAY,
      OnAccept = function(self)
      end,
      timeout = 0,
      whileDead = 1,
      hideOnEscape = 1
    }
    
    StaticPopup_Show("QH_EXPLODEY")
    yelled_at_user = true
  end
end

function QuestHelper_WarningCatcher_GenerateReport()
  if first_warning then return end -- don't need to generate one
  
  local twarning = QuestHelper_WarningPackage()
  
  twarning.message = "(Full report)"
  twarning.addons = QuestHelper_WarningCatcher.GetAddOns()
  twarning.stack = ""
  twarning.silent = "(Full report)"
  twarning.generated = true
  twarning.quests = QuestHelper_WarningCatcher.GetQuests()
  
  first_warning = twarning
end

function QuestHelper_WarningCatcher.OnWarning(o_msg, o_frame, o_stack, o_etype, ...)
  local warningize = false
  local loud = false
  if o_msg and string.find(o_msg, "QuestHelper") and not string.find(o_msg, "Cannot find a library with name") then loud = true end
  
  for lin in string.gmatch(debugstack(2, 20, 20), "([^\n]*)") do
    if string.find(lin, "QuestHelper") and not string.find(lin, "QuestHelper\\AstrolabeQH\\DongleStub.lua") then warningize = true end
  end
  
  if string.find(o_msg, "SavedVariables") then warningize, loud = false, false end
  if string.find(o_msg, "C stack overflow") then
    if loud then warningize = true end
    loud = false
  end
  
  if loud then warningize = true end
  
  if warningize then QuestHelper_WarningCatcher_ExplicitWarning(loud, o_msg, o_frame, o_stack) end
  
  --[[
  if o_msg and
    (
      (
        string.find(o_msg, "QuestHelper")  -- Obviously we care about our bugs
      )
      or (
        string.find(debugstack(2, 20, 20), "QuestHelper")  -- We're being a little overzealous and catching any bug with "QuestHelper" in the stack. This possibly should be removed, I'm not sure it's ever caught anything interesting.
        and not string.find(o_msg, "Cartographer_POI")  -- Cartographer started throwing ridiculous numbers of warnings on startup with QH in the stack, and since we caught stuff with QH in the stack, we decided these warnings were ours. Urgh. Disabled.
      )
    )
    and not string.match(o_msg, "WTF\\Account\\.*")  -- Sometimes the WTF file gets corrupted. This isn't our fault, since we weren't involved in writing it, and there's also nothing we can do about it - in fact we can't even retrieve the remnants of the old file. We may as well just ignore it. I suppose we could pop up a little dialog saying "clear some space on your hard drive, dufus" but, meh.
    and not (string.find(o_msg, "Cannot find a library with name") and string.find(debugstack(2, 20, 20), "QuestHelper\\AstrolabeQH\\DongleStub.lua")) -- We're catching warnings caused by other people mucking up their dongles. Ughh.
    then
      QuestHelper_WarningCatcher_ExplicitWarning(o_msg, o_frame, o_stack)
  end]]
  
  return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {}))  -- pass it on
end

setwarninghandler(QuestHelper_WarningCatcher.OnWarning) -- at this point we can catch warnings

function QuestHelper_WarningCatcher.CompletelyStarted()
  completely_started = true
  
  -- Our old code generated a horrifying number of redundant items. My bad. I considered going and trying to collate them into one chunk, but I think I'm just going to wipe them - it's easier, faster, and should fix some performance issues.
  if not QuestHelper_Warnings.version or QuestHelper_Warnings.version ~= 1 then
    QuestHelper_Warnings = {version = 1}
  end
  
  QuestHelper_WarningCatcher.CondenseWarnings()
end

function QuestHelper_WarningCatcher_CompletelyStarted()
  QuestHelper_WarningCatcher.CompletelyStarted()
end



-- and here is the GUI

local QHE_Gui = {}

function QHE_Gui.WarningUpdate()
  QHE_Gui.WarningTextinate()
  QHE_Gui.Warning.Box:SetText(QHE_Gui.Warning.curWarning)
  QHE_Gui.Warning.Scroll:UpdateScrollChildRect()
	QHE_Gui.Warning.Box:ClearFocus()
end

function TextinateWarning(warn)
  local tswarn = string.format("msg: %s\ntoc: %s\nv: %s\ngame: %s\nlocale: %s\ntimestamp: %s\nmutation: %s\nsilent: %s\n\n%s\naddons:\n%s", warn.message, warn.toc_version, warn.local_version, warn.game_version, warn.locale, warn.timestamp, tostring(warn.mutation_passes_exceeded), tostring(warn.silent), warn.stack, warn.addons)
  if warn.next_loud then
    tswarn = tswarn .. "\n\n---- Following loud warning\n\n" .. TextinateWarning(warn.next_loud)
  end
  return tswarn
end

function QHE_Gui.WarningTextinate()
  if first_warning then
    QHE_Gui.Warning.curWarning = TextinateWarning(first_warning)
  else
    QHE_Gui.Warning.curWarning = "None"
  end
end

function QHE_Gui.WarningClicked()
	if (QHE_Gui.Warning.selected) then return end
	QHE_Gui.Warning.Box:HighlightText()
	QHE_Gui.Warning.selected = true
end

function QHE_Gui.WarningDone()
	QHE_Gui.Warning:Hide()
end


-- Create our warning message frame. Most of this is also ganked from Swatter.
QHE_Gui.Warning = CreateFrame("Frame", "QHE_GUIWarningFrame", UIParent)
QHE_Gui.Warning:Hide()
QHE_Gui.Warning:SetPoint("CENTER", "UIParent", "CENTER")
QHE_Gui.Warning:SetFrameStrata("TOOLTIP")
QHE_Gui.Warning:SetHeight(300)
QHE_Gui.Warning:SetWidth(600)
QHE_Gui.Warning:SetBackdrop({
	bgFile = "Interface/Tooltips/ChatBubble-Background",
	edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
	tile = true, tileSize = 32, edgeSize = 32,
	insets = { left = 32, right = 32, top = 32, bottom = 32 }
})
QHE_Gui.Warning:SetBackdropColor(0.2,0,0, 1)
QHE_Gui.Warning:SetScript("OnShow", QHE_Gui.WarningShow)
QHE_Gui.Warning:SetMovable(true)

QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Warning)
QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Warning:IsShown() end
QHE_Gui.ProxyFrame.escCount = 0
QHE_Gui.ProxyFrame.timer = 0
QHE_Gui.ProxyFrame.Hide = (
	function( self )
		local numEscapes = QHE_Gui.numEscapes or 1
		self.escCount = self.escCount + 1
		if ( self.escCount >= numEscapes ) then
			self:GetParent():Hide()
			self.escCount = 0
		end
		if ( self.escCount == 1 ) then
			self.timer = 0
		end
	end
)
QHE_Gui.ProxyFrame:SetScript("OnUpdate",
	function( self, elapsed )
		local timer = self.timer + elapsed
		if ( timer >= 1 ) then
			self.escCount = 0
		end
		self.timer = timer
	end
)
table.insert(UISpecialFrames, "QHE_GuiProxyFrame")

QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Warning)
QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Warning, "TOPLEFT", 10,-5)
QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Warning, "TOPRIGHT", -10,-5)
QHE_Gui.Drag:SetHeight(8)
QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")

QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Warning:StartMoving() end)
QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Warning:StopMovingOrSizing() end)

QHE_Gui.Warning.Done = CreateFrame("Button", "", QHE_Gui.Warning, "OptionsButtonTemplate")
QHE_Gui.Warning.Done:SetText("Close")
QHE_Gui.Warning.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Warning, "BOTTOMRIGHT", -10, 10)
QHE_Gui.Warning.Done:SetScript("OnClick", QHE_Gui.WarningDone)

QHE_Gui.Warning.Mesg = QHE_Gui.Warning:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
QHE_Gui.Warning.Mesg:SetJustifyH("LEFT")
QHE_Gui.Warning.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Warning.Prev, "TOPLEFT", -10, 0)
QHE_Gui.Warning.Mesg:SetPoint("LEFT", QHE_Gui.Warning, "LEFT", 15, 0)
QHE_Gui.Warning.Mesg:SetHeight(20)
QHE_Gui.Warning.Mesg:SetText("Select All and Copy the above warning message to report this bug.")

QHE_Gui.Warning.Scroll = CreateFrame("ScrollFrame", "QHE_GUIWarningInputScroll", QHE_Gui.Warning, "UIPanelScrollFrameTemplate")
QHE_Gui.Warning.Scroll:SetPoint("TOPLEFT", QHE_Gui.Warning, "TOPLEFT", 20, -20)
QHE_Gui.Warning.Scroll:SetPoint("RIGHT", QHE_Gui.Warning, "RIGHT", -30, 0)
QHE_Gui.Warning.Scroll:SetPoint("BOTTOM", QHE_Gui.Warning.Done, "TOP", 0, 10)

QHE_Gui.Warning.Box = CreateFrame("EditBox", "QHE_GUIWarningEditBox", QHE_Gui.Warning.Scroll)
QHE_Gui.Warning.Box:SetWidth(500)
QHE_Gui.Warning.Box:SetHeight(85)
QHE_Gui.Warning.Box:SetMultiLine(true)
QHE_Gui.Warning.Box:SetAutoFocus(false)
QHE_Gui.Warning.Box:SetFontObject(GameFontHighlight)
QHE_Gui.Warning.Box:SetScript("OnEscapePressed", QHE_Gui.WarningDone)
QHE_Gui.Warning.Box:SetScript("OnTextChanged", QHE_Gui.WarningUpdate)
QHE_Gui.Warning.Box:SetScript("OnEditFocusGained", QHE_Gui.WarningClicked)

QHE_Gui.Warning.Scroll:SetScrollChild(QHE_Gui.Warning.Box)

function QuestHelper_WarningCatcher_ReportWarning()
  QHE_Gui.Warning.selected = false
	QHE_Gui.WarningUpdate()
	QHE_Gui.Warning:Show()
end
